{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# `Nuqleon.Json.Serialization`\n",
        "\n",
        "Provides fast JSON serialization for objects using runtime code compilation for specialized parsers and printers.\n",
        "\n",
        "> **Note:** The functionality in this assembly has been used to provide high throughput serialization and deserialization using JSON, for objects of a given static type. It allows for efficient skipping of tokens that are irrevelant to the object being deserialized, beating performance of `Newtonsoft.Json` hands down. Alternatives with `System.Text.Json` have not been explored; this assembly predates the inclusion of JSON support in .NET by several years."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Reference the library"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 1 - Use a local build\n",
        "\n",
        "If you have built the library locally, run the following cell to load the latest build."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"bin/Debug/net6.0/Nuqleon.Json.Serialization.dll\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Option 2 - Use NuGet packages\n",
        "\n",
        "If you want to use the latest published package from NuGet, run the following cell."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "#r \"nuget:Nuqleon.Json.Serialization,*-*\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## (Optional) Attach a debugger\n",
        "\n",
        "If you'd like to step through the source code of the library while running samples, run the following cell, and follow instructions to start a debugger (e.g. Visual Studio). Navigate to the source code of the library to set breakpoints."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "System.Diagnostics.Debugger.Launch();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Strongly typed JSON serialization\n",
        "\n",
        "The serializer provided by this library is parameterized on a data type which is used to derive efficient parsers and printers at runtime. To illustrate this capability, we'll first define a custom type."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "record Person\n",
        "{\n",
        "    public string Name { get; init; }\n",
        "    public int Age { get; init; }\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Serialize\n",
        "\n",
        "To serialize objects to JSON, we first create an `IFastJsonSerializer<T>` by using the `FastJsonSerializerFactory.CreateSerializer<T>(INameProvider, FastJsonSerializerSettings)` method. The two parameters provided are:\n",
        "\n",
        "* An `INameProvider` to convert property and field names to the names used in the corresponding JSON objects.\n",
        "* Settings represented as a `FastJsonSerializerSettings` object. This currently only consist of a concurrency mode, but may be extended in the future.\n",
        "\n",
        "To illustrate serialization, let's have a look at creating a serializer first."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "using Nuqleon.Json.Serialization;\n",
        "\n",
        "IFastJsonSerializer<Person> serializer = FastJsonSerializerFactory.CreateSerializer<Person>(DefaultNameProvider.Instance, new FastJsonSerializerSettings(FastJsonConcurrencyMode.SingleThreaded));"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "In this configuration, we use the default name provider which will use the `Name` and `Age` property names when emitting a JSON object for a `Person` instance. The serializer settings specify a `SingleThreaded` concurrency mode, which makes the serializer only safe to run from a single thread. This enables higher throughput compared to a concurrency-safe instance of a serializer.\n",
        "\n",
        "Next, we can use the serializer to serialize a `Person` instance."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "{\"Age\":21,\"Name\":\"Bart\"}\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "var p = new Person { Name = \"Bart\", Age = 21 };\n",
        "\n",
        "string json = serializer.Serialize(p);\n",
        "\n",
        "Console.WriteLine(json);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Another overload of `Serialize` accepts a `TextWriter` to append the JSON string to, thus avoiding the allocation of an intermediate string."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "At a first glance, this doesn't look much different from other serialization frameworks. However, by passing the `Person` type to `CreateSerializer<T>`, the library can generate optimized code to perform serialization. Let's run a small benchmark to prove this point.\n",
        "\n",
        "First, let's reference Newtonsoft.Json."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "Installed package Newtonsoft.Json version 12.0.3"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "#r \"nuget:Newtonsoft.Json\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Next, we'll define a small benchmark suite utility using `Nuqleon.Time`'s support to build custom stopwatches to measure bytes allocated."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "using System.Time;\n",
        "\n",
        "class MemoryClock : IClock\n",
        "{\n",
        "    public long Now => GC.GetAllocatedBytesForCurrentThread();\n",
        "}\n",
        "\n",
        "IStopwatch swMem = StopwatchFactory.FromClock(new MemoryClock()).Create();\n",
        "IStopwatch swTime = StopwatchFactory.Diagnostics.Create();\n",
        "\n",
        "void Benchmark(string title, Action test, int n)\n",
        "{\n",
        "    swMem.Restart();\n",
        "    swTime.Restart();\n",
        "\n",
        "    for (int i = 0; i < n; i++)\n",
        "    {\n",
        "        test();\n",
        "    }\n",
        "\n",
        "    swTime.Stop();\n",
        "    swMem.Stop();\n",
        "\n",
        "    Console.WriteLine($\"{title} completed in {swTime.ElapsedMilliseconds} ms and allocated {swMem.ElapsedTicks} bytes.\");\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Now we can define two serializer functions, one using Newtonsoft and one using Nuqleon. In both cases, we'll use `StringWriter.Null` to emit the JSON, so we just account for the overheads of the library used."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "using System.IO;\n",
        "using System.Text;\n",
        "using Newtonsoft.Json;\n",
        "\n",
        "var serN = new Newtonsoft.Json.JsonSerializer();\n",
        "var serJ = FastJsonSerializerFactory.CreateSerializer<Person>(DefaultNameProvider.Instance, new FastJsonSerializerSettings(FastJsonConcurrencyMode.SingleThreaded));\n",
        "\n",
        "void TestN() => serN.Serialize(StringWriter.Null, p);\n",
        "void TestJ() => serJ.Serialize(p, StringWriter.Null);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Finally, let's run the benchmark."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "Newtonsoft completed in 632 ms and allocated 448201408 bytes.\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "Nuqleon completed in 98 ms and allocated 216 bytes.\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "Benchmark(\"Newtonsoft\", TestN, 1_000_000);\n",
        "Benchmark(\"Nuqleon\", TestJ, 1_000_000);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Note that Nuqleon does not allocate any bytes. No intermediate data structures are built to facilitate serialization. The genenerated JSON serialization code is specialized for `Person`, traverses the object graph, and writes directly to the text writer."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Deserialize\n",
        "\n",
        "To deserialize objects from JSON, we first create an `IFastJsonDeserializer<T>` by using the `FastJsonSerializerFactory.CreateDeserializer<T>(INameResolver, FastJsonSerializerSettings)` method. The two parameters provided are:\n",
        "\n",
        "* An `INameResolver` to map a key in a JSON object to the property or field to assign the deserialized value to.\n",
        "* Settings represented as a `FastJsonSerializerSettings` object. This currently only consist of a concurrency mode, but may be extended in the future.\n",
        "\n",
        "To illustrate deserialization, let's have a look at creating a deserializer first."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "IFastJsonDeserializer<Person> deserializer = FastJsonSerializerFactory.CreateDeserializer<Person>(DefaultNameResolver.Instance, new FastJsonSerializerSettings(FastJsonConcurrencyMode.SingleThreaded));"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "In this configuration, we use the default name resolver which will use the `Name` and `Age` properties while constructing a `Person` instance. The serializer settings specify a `SingleThreaded` concurrency mode, which makes the serializer only safe to run from a single thread. This enables higher throughput compared to a concurrency-safe instance of a deserializer.\n",
        "\n",
        "Next, we can use the deserializer to deserialize a `Person` instance."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "Person { Name = Bart, Age = 21 }\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "var json = \"{\\\"Name\\\": \\\"Bart\\\", \\\"Age\\\": 21, \\\"Hobbies\\\": [ \\\"Code\\\", \\\"Walk\\\", \\\"Run\\\" ]}\";\n",
        "\n",
        "Person p = deserializer.Deserialize(json);\n",
        "\n",
        "Console.WriteLine(p);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Note that the JSON string we deserialize from contains an additional `Hobbies` property which gets dropped on the floor. This is used to illustrate the strenght of the Nuqleon serializer in terms of efficiency. Because the deserializer is built at runtime from reflecting on the structure of the `Person` type, it can efficiently skip over the whole JSON array and avoid allocating tokens, objects, or even .NET arrays to represent the value that's not needed to construct the `Person` object."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Just like we did earlier for the serializer, let's define a little benchmark to compare the performance of deserialization across different JSON libraries."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [],
      "source": [
        "var serN = new Newtonsoft.Json.JsonSerializer();\n",
        "var serJ = FastJsonSerializerFactory.CreateDeserializer<Person>(DefaultNameResolver.Instance, new FastJsonSerializerSettings(FastJsonConcurrencyMode.SingleThreaded));\n",
        "\n",
        "void TestN() => serN.Deserialize(new StringReader(json), typeof(Person));\n",
        "void TestJ() => serJ.Deserialize(json);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Finally, let's run the benchmark."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "dotnet_interactive": {
          "language": "csharp"
        }
      },
      "outputs": [
        {
          "data": {
            "text/plain": "Newtonsoft completed in 1780 ms and allocated 2736021672 bytes.\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": "Nuqleon completed in 931 ms and allocated 64000048 bytes.\r\n"
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "Benchmark(\"Newtonsoft\", TestN, 1_000_000);\n",
        "Benchmark(\"Nuqleon\", TestJ, 1_000_000);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Again, note how the Nuqleon library avoids a lot of unnecessary allocations. There's also no need to allocate an intermediate `TextReader` to deserialize from a string."
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": ".NET (C#)",
      "language": "C#",
      "name": ".net-csharp"
    },
    "language_info": {
      "file_extension": ".cs",
      "mimetype": "text/x-csharp",
      "name": "C#",
      "pygments_lexer": "csharp",
      "version": "9.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}